/*
** $Id: ltm.c $
** Tag methods
** See Copyright Notice in lua.h
*/

/*
** 此文件定义了Lua中的标签方法。
** 标签方法是一种机制，用于在Lua中为不同的数据类型定义自定义操作。
** 它们允许用户为特定类型的数据定义如比较、求值等操作。
*/

#define ltm_c
#define LUA_CORE

#include "lprefix.h"


#include <string.h>

#include "lua.h"

/*
** 包含Lua核心功能的头文件。
** 这些头文件定义了Lua的状态、基本数据结构和操作函数。
*/

#include "ldebug.h"
#include "ldo.h"
#include "lgc.h"
#include "lobject.h"
#include "lstate.h"
#include "lstring.h"
#include "ltable.h"
#include "ltm.h"
#include "lvm.h"
/*
** 包含Lua虚拟机和相关功能的头文件。
** 这些头文件定义了Lua如何执行字节码、如何处理调用、返回和错误等。
*/


/*
 * 定义了Lua类型名称的数组。
 * 这个数组包含Lua中所有类型的名称，包括用户自定义数据类型。
 * 其中一些类型名称是专门预留用于测试的。
 */

// 定义一个静态常量字符数组，用于保存用户数据类型的名称。
static const char udatatypename[] = "userdata";

// 定义一个常量数组，包含了Lua所有类型的名称。
// 这个数组让Lua的类型检查和类型名称获取变得更加简单。
LUAI_DDEF const char *const luaT_typenames_[LUA_TOTALTYPES] = {
  "no value", // 代表没有值的类型
  "nil", "boolean", udatatypename, "number", // 基本类型：nil、boolean、userdata、number
  "string", "table", "function", udatatypename, "thread", // 复杂类型：string、table、function、userdata、thread
  "upvalue", "proto" /* these last cases are used for tests only */ // 内部使用类型：upvalue、proto
};


/**
 * 初始化Lua表相关的操作符和事件名称。
 * 
 * 该函数负责在Lua状态机中初始化所有预定义的操作符和事件的名称。这些名称主要用于
 * 特定的表操作，例如索引、新索引、垃圾回收等。该函数将每个事件名称存储在全局表
 * 的相应位置，以便Lua在执行时可以快速访问它们。
 * 
 * @param L 指向当前Lua状态机的指针。
 */

/* 初始化操作符和事件名称 */
void luaT_init (lua_State *L) {
  /* 定义操作符和事件名称的数组。注：该数组必须按照特定的顺序排列。包括了Lua中各种特殊方法的名称。 */
  static const char *const luaT_eventname[] = {  
   /* "__index", "__newindex",
    "__gc", "__mode", "__len", "__eq",
    "__add", "__sub", "__mul", "__mod", "__pow",
    "__div", "__idiv",
    "__band", "__bor", "__bxor", "__shl", "__shr",
    "__unm", "__bnot", "__lt", "__le",
    "__concat", "__call", "__close",   中文关键字 */
    "__索引","__新索引",                             
    "__垃圾回收", "__模式", "__长度", "__等于",
    "__加", "__减", "__乘", "__模", "__幂",
    "__除", "__整除",
    "__位与", "__位或", "__位异或", "__左移", "__右移",
    "__负", "__位非", "__小于", "__小于等于",
    "__连接", "__调用", "__关闭"
  };
  int i;

  /* 遍历数组，将每个操作符和事件名称添加到Lua的全局表中 */
  for (i=0; i<TM_N; i++) {
    G(L)->tmname[i] = luaS_new(L, luaT_eventname[i]);
    /* 确保这些名称不会被垃圾回收机制收集 */
    luaC_fix(L, obj2gco(G(L)->tmname[i]));
  }
}

/*
** 此函数与宏 "fasttm" 配合使用：优化了没有标签方法的情况
*/
const TValue *luaT_gettm (Table *events, TMS event, TString *ename) {
  /* 尝试从表中获取对应事件的标签方法 */
  const TValue *tm = luaH_getshortstr(events, ename);
  lua_assert(event <= TM_EQ);  /* 确保事件类型在可接受范围内 */
  
  /* 如果没有找到标签方法，则在表中设置相应事件的缓存标志 */
  if (notm(tm)) {  
    events->flags |= cast_byte(1u<<event); 
    return NULL;
  }
  else return tm;  /* 如果找到了标签方法，则返回该方法 */
}


/**
 * 根据给定的对象和事件类型，获取对应的元方法。
 * 
 * @param L lua_State的指针，表示当前的Lua环境。
 * @param o 要查询元方法的对象。
 * @param event 元方法的事件类型，使用TMS枚举类型表示。
 * @return 返回一个指向元方法的TValue指针。如果没有找到对应的元方法，则返回全局表中的nil值。
 */
const TValue *luaT_gettmbyobj (lua_State *L, const TValue *o, TMS event) {
  Table *mt; // 将要查询的元方法所在的表
  switch (ttype(o)) { // 根据对象的类型决定查询哪个表
    case LUA_TTABLE:
      mt = hvalue(o)->metatable; // 查询表类型的元方法表
      break;
    case LUA_TUSERDATA:
      mt = uvalue(o)->metatable; // 查询用户数据类型的元方法表
      break;
    default:
      mt = G(L)->mt[ttype(o)]; // 查询其他类型的默认元方法表
  }
  // 尝试从mt表中获取对应事件的元方法，如果未找到则返回nil
  return (mt ? luaH_getshortstr(mt, G(L)->tmname[event]) : &G(L)->nilvalue);
}


/*
** 返回一个对象类型的名称。对于具有元表的表和用户数据，
** 如果存在，使用它们的'__name'元字段。
*/
const char *luaT_objtypename (lua_State *L, const TValue *o) {
  Table *mt;
  // 检查是否为表或带有元表的完整用户数据
  if ((ttistable(o) && (mt = hvalue(o)->metatable) != NULL) ||
      (ttisfulluserdata(o) && (mt = uvalue(o)->metatable) != NULL)) {
    // 尝试从元表中获取'__name'字段
    const TValue *name = luaH_getshortstr(mt, luaS_new(L, "__name"));
    if (ttisstring(name))  /* '__name'是否为字符串？ */
      return getstr(tsvalue(name));  /* 使用它作为类型名称 */
  }
  // 如果没有找到'__name'，则使用标准类型名称
  return ttypename(ttype(o));  
}


/**
 * 调用TM（元方法）函数。
 * 
 * @param L 指向当前Lua状态机的指针。
 * @param f 指向要调用的函数的TValue指针。
 * @param p1 第一个参数的TValue指针。
 * @param p2 第二个参数的TValue指针。
 * @param p3 第三个参数的TValue指针。
 * 该函数不返回任何值。
 */
void luaT_callTM (lua_State *L, const TValue *f, const TValue *p1,
                  const TValue *p2, const TValue *p3) {
  StkId func = L->top.p; // 获取当前栈顶位置，并准备放置函数和参数。
  setobj2s(L, func, f);  /* 将函数压入栈中 */
  setobj2s(L, func + 1, p1);  /* 将第一个参数压入栈中 */
  setobj2s(L, func + 2, p2);  /* 将第二个参数压入栈中 */
  setobj2s(L, func + 3, p3);  /* 将第三个参数压入栈中 */
  L->top.p = func + 4; // 更新栈顶位置，表示所有参数均已压入栈中。
  
  /* 根据调用是否来自Lua代码，决定是否允许元方法产生yield */
  if (isLuacode(L->ci))
    luaD_call(L, func, 0); // 如果调用来自Lua代码，则允许yield。
  else
    luaD_callnoyield(L, func, 0); // 如果调用不来自Lua代码，则不允许yield。
}


/**
 * 调用TM（元方法）并处理结果
 * 
 * @param L 指向当前Lua状态机的指针
 * @param f 要调用的函数或元方法的TValue指针
 * @param p1 第一个参数的TValue指针
 * @param p2 第二个参数的TValue指针
 * @param res 用于存放函数返回结果的栈位置
 */
void luaT_callTMres (lua_State *L, const TValue *f, const TValue *p1,
                     const TValue *p2, StkId res) {
  // 保存res相对栈顶的位置
  ptrdiff_t result = savestack(L, res);
  StkId func = L->top.p;
  // 将函数和两个参数压入栈中
  setobj2s(L, func, f);  /* push function (assume EXTRA_STACK) */
  setobj2s(L, func + 1, p1);  /* 1st argument */
  setobj2s(L, func + 2, p2);  /* 2nd argument */
  L->top.p += 3;  // 更新栈顶指针
  // 根据调用是否来自Lua代码，决定是否允许元方法产生yield
  if (isLuacode(L->ci))
    luaD_call(L, func, 1);  // 调用Lua代码可以yield
  else
    luaD_callnoyield(L, func, 1);  // 调用非Lua代码不允许yield
  // 恢复栈顶位置并移动结果到正确的位置
  res = restorestack(L, result);
  setobjs2s(L, res, --L->top.p);  /* move result to its place */
}


/*
 * 调用相应的TM（Lua中的类型转换机制）函数。
 *
 * @param L lua_State的指针，表示当前的Lua环境。
 * @param p1 第一个操作数的TValue指针。
 * @param p2 第二个操作数的TValue指针。
 * @param res 用于存放结果的栈位置的StkId。
 * @param event 触发TM事件的类型。
 * @return 如果成功调用TM函数，则返回1；如果没有相应的TM函数，则返回0。
 */
static int callbinTM (lua_State *L, const TValue *p1, const TValue *p2,
                      StkId res, TMS event) {
  /* 尝试获取第一个操作数对应的TM函数 */
  const TValue *tm = luaT_gettmbyobj(L, p1, event);  
  if (notm(tm)) /* 如果第一个操作数没有对应的TM函数 */
    tm = luaT_gettmbyobj(L, p2, event);  /* 尝试获取第二个操作数对应的TM函数 */
  if (notm(tm)) return 0; /* 如果两个操作数都没有对应的TM函数，则返回0 */
  /* 调用获取到的TM函数，并将结果存放在res指定的栈位置 */
  luaT_callTMres(L, tm, p1, p2, res);
  return 1; /* 表示成功调用了TM函数 */
}


/**
 * 尝试调用二元操作符的对应 TM (Tag Method) 函数。
 * 如果调用失败，根据操作类型给出相应的错误提示。
 * 
 * @param L lua_State的指针，表示当前的Lua环境。
 * @param p1 操作数1的TValue指针。
 * @param p2 操作数2的TValue指针。
 * @param res 用于存放结果的栈位置的指针。
 * @param event 表示当前操作类型的枚举值（如TM_ADD，TM_SUB等）。
 */
void luaT_trybinTM (lua_State *L, const TValue *p1, const TValue *p2,
                    StkId res, TMS event) {
  if (l_unlikely(!callbinTM(L, p1, p2, res, event))) { // 尝试调用二元TM，如果失败则处理错误
    switch (event) { // 根据操作类型决定错误处理方式
      case TM_BAND: case TM_BOR: case TM_BXOR: // 按位操作
      case TM_SHL: case TM_SHR: case TM_BNOT: { // 左移、右移和按位取反操作
        if (ttisnumber(p1) && ttisnumber(p2)) // 如果两个操作数都是数字
          luaG_tointerror(L, p1, p2); // 转换错误
        else
          luaG_opinterror(L, p1, p2, "perform bitwise operation on"); // 执行位操作时的错误
      }
      /* calls never return, but to avoid warnings: *//* FALLTHROUGH */
      default: // 默认情况，适用于其他所有二元操作
        luaG_opinterror(L, p1, p2, "perform arithmetic on"); // 执行算术操作时的错误
    }
  }
}


/*
 * 尝试调用Lua中的二元操作符重载模板（concatenation模板）。
 * 如果操作不成功，则抛出拼接错误。
 * @param L Lua状态机指针。
 */
void luaT_tryconcatTM (lua_State *L) {
  StkId top = L->top.p; // 获取当前栈顶位置
  // 尝试调用concat模板，如果不成功，则抛出拼接错误
  if (l_unlikely(!callbinTM(L, s2v(top - 2), s2v(top - 1), top - 2,
                               TM_CONCAT)))
    luaG_concaterror(L, s2v(top - 2), s2v(top - 1));
}

/*
 * 尝试调用二元操作的关联模板。
 * 根据`flip`标志决定是调用p1操作数与p2操作数的模板，还是调用p2与p1的模板。
 * @param L Lua状态机指针。
 * @param p1 操作数1的指针。
 * @param p2 操作数2的指针。
 * @param flip 标志位，决定操作数的调用顺序。
 * @param res 期望的结果位置。
 * @param event 触发的模板事件类型。
 */
void luaT_trybinassocTM (lua_State *L, const TValue *p1, const TValue *p2,
                                       int flip, StkId res, TMS event) {
  if (flip)
    luaT_trybinTM(L, p2, p1, res, event); // 调用p2与p1的模板
  else
    luaT_trybinTM(L, p1, p2, res, event); // 调用p1与p2的模板
}

/*
 * 尝试调用一个整数和一个非整数之间的二元操作模板。
 * @param L Lua状态机指针。
 * @param p1 非整数操作数的指针。
 * @param i2 整数操作数。
 * @param flip 标志位，决定操作数的调用顺序。
 * @param res 期望的结果位置。
 * @param event 触发的模板事件类型。
 */
void luaT_trybiniTM (lua_State *L, const TValue *p1, lua_Integer i2,
                                   int flip, StkId res, TMS event) {
  TValue aux; // 用于存放整数操作数的临时TValue
  setivalue(&aux, i2); // 将整数设置到aux中
  luaT_trybinassocTM(L, p1, &aux, flip, res, event); // 调用二元操作的关联模板
}


/*
** 调用一个排序标签方法。
** 对于小于等于操作，如果定义了 LUA_COMPAT_LT_LE，为了保持向后兼容性，
** 会尝试使用 '__le' 进行比较；如果没有定义 '__le'，则尝试 '__lt'，
** 基于 l <= r 当且仅当 !(r < l) 的逻辑（假设为完全排序）。
** 如果在这种替代过程中元方法产生yield，那么继续执行时需要知道这个信息
** （以便否定 r<l 的结果）；调用状态中的位 CIST_LEQ 保持这一信息。
*/
int luaT_callorderTM (lua_State *L, const TValue *p1, const TValue *p2,
                      TMS event) {
  /* 尝试调用原始事件的二元操作符元方法 */
  if (callbinTM(L, p1, p2, L->top.p, event))  
    return !l_isfalse(s2v(L->top.p));
#if defined(LUA_COMPAT_LT_LE)
  /* 如果是小于等于操作，并且定义了 LUA_COMPAT_LT_LE，尝试 !(p2 < p1) 来实现 (p1 <= p2) */
  else if (event == TM_LE) {
      L->ci->callstatus |= CIST_LEQ;  /* 标记当前正在为了 'le' 执行 'lt' */
      if (callbinTM(L, p2, p1, L->top.p, TM_LT)) {
        L->ci->callstatus ^= CIST_LEQ;  /* 清除标记 */
        return l_isfalse(s2v(L->top.p));
      }
      /* 如果发生错误，会移除当前的 'ci'，无需清除标记 */
  }
#endif
  /* 没有找到元方法时抛出错误 */
  luaG_ordererror(L, p1, p2);  
  return 0;  /* 避免警告 */
}


/*
 * 调用排序TM函数
 * 
 * 此函数用于调用Lua中的排序TM（元方法）函数。它会根据提供的参数类型和值，以及某些标志来准备调用环境，
 * 然后实际调用排序TM函数。
 * 
 * @param L lua_State的指针，表示当前的Lua环境。
 * @param p1 指向第一个比较对象的TValue指针。
 * @param v2 第二个比较对象的值。如果isfloat为真，则v2被视为浮点数；否则视为整数。
 * @param flip 标志位，如果为真，则表示两个比较对象的位置需要交换。
 * @param isfloat 标志位，如果为真，则表示v2是一个浮点数。
 * @param event TMS事件类型，指定调用的元方法类型（如__lt, __le等）。
 * @return 返回调用luaT_callorderTM的结果，通常是一个整数。
 */
int luaT_callorderiTM (lua_State *L, const TValue *p1, int v2,
                       int flip, int isfloat, TMS event) {
  TValue aux; const TValue *p2;
  
  // 根据isfloat标志，将v2转换为TValue类型的浮点数或整数值。
  if (isfloat) {
    setfltvalue(&aux, cast_num(v2));
  }
  else
    setivalue(&aux, v2);
  
  // 如果flip标志为真，则交换p1和p2的位置，以准备调用排序TM函数。
  if (flip) {  
    p2 = p1; p1 = &aux;  /* 交换两个比较对象的位置 */
  }
  else
    p2 = &aux;
  
  // 调用排序TM函数，并返回其结果。
  return luaT_callorderTM(L, p1, p2, event);
}


/*
 * 调整可变参数的数量，将函数和固定参数复制到堆栈顶部，并更新调用信息。
 * 
 * @param L lua_State的指针，表示当前的Lua环境。
 * @param nfixparams 固定参数的数量。
 * @param ci CallInfo的指针，表示当前的调用信息。
 * @param p Proto的指针，表示当前函数的原型信息。
 */
void luaT_adjustvarargs (lua_State *L, int nfixparams, CallInfo *ci,
                         const Proto *p) {
  int i;
  int actual = cast_int(L->top.p - ci->func.p) - 1;  /* 计算实际传入的参数数量 */
  int nextra = actual - nfixparams;  /* 计算额外的参数数量 */
  ci->u.l.nextraargs = nextra;
  /* 检查堆栈是否足够大 */
  luaD_checkstack(L, p->maxstacksize + 1);
  /* 将函数复制到堆栈顶部 */
  setobjs2s(L, L->top.p++, ci->func.p);
  /* 将固定参数移动到堆栈顶部 */
  for (i = 1; i <= nfixparams; i++) {
    setobjs2s(L, L->top.p++, ci->func.p + i);
    setnilvalue(s2v(ci->func.p + i));  /* 清空原来的参数值，以便垃圾回收 */
  }
  /* 更新函数和堆栈顶部的位置 */
  ci->func.p += actual + 1;
  ci->top.p += actual + 1;
  /* 断言确保堆栈的使用在合理范围内 */
  lua_assert(L->top.p <= ci->top.p && ci->top.p <= L->stack_last.p);
}


/**
 * 将函数调用中的可变参数复制到指定的位置。
 * 
 * @param L lua_State的指针，表示当前的Lua环境。
 * @param ci 指向当前调用信息的指针。
 * @param where 指定复制参数的起始位置。
 * @param wanted 需要复制的参数数量。如果为负值，则表示复制所有可用的额外参数。
 */
void luaT_getvarargs (lua_State *L, CallInfo *ci, StkId where, int wanted) {
  int i;
  int nextra = ci->u.l.nextraargs;  // 获取函数调用中额外参数的数量。
  
  if (wanted < 0) {
    wanted = nextra;  /* 如果wanted为负，表示想要所有额外参数，此时将wanted设置为nextra */
    checkstackGCp(L, nextra, where);  /* 检查并确保栈的空间足够 */
    L->top.p = where + nextra;  /* 更新栈顶指针，为接下来的操作做准备 */
  }

  // 将函数调用中的额外参数复制到指定的位置
  for (i = 0; i < wanted && i < nextra; i++)
    setobjs2s(L, where + i, ci->func.p - nextra + i);

  // 如果wanted大于实际的额外参数数量，用nil填充剩余的位置
  for (; i < wanted; i++)  
    setnilvalue(s2v(where + i));
}

